package cn.amossun.starter.event;

import cn.amossun.starter.event.endpoint.EventContextEndpoint;
import cn.amossun.starter.event.handler.*;
import cn.amossun.starter.event.listener.SimpleRemoteEventMessageListener;
import cn.amossun.starter.event.property.EventContext;
import cn.amossun.starter.event.property.ListenerContext;
import org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.amqp.DirectRabbitListenerContainerFactoryConfigurer;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.Environment;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class EventAutoConfiguration {

    @Value("${spring.application.name}")
    private String service;

    @Bean(name = "instance")
    String instance(Environment environment) throws UnknownHostException {
        return String.format("%s:%s:%s", InetAddress.getLocalHost().getHostAddress(), service, environment.getProperty("server.port"));
    }

    @Bean
    EventContext eventContext() {
        return new EventContext();
    }

    @Bean
    EventQueueContextProcessor eventQueueContextProcessor(EventContext eventContext){
        return new EventQueueContextProcessor(eventContext);
    }

    @Bean
    EventExchangeContextProcessor eventExchangeContextProcessor(EventContext eventContext){
        return new EventExchangeContextProcessor(eventContext);
    }

    @Bean
    EventBindingContextProcessor eventBindingContextProcessor(EventContext eventContext,
                                                              EventQueueContextProcessor eventQueueContextProcessor,
                                                              EventExchangeContextProcessor eventExchangeContextProcessor){
        return new EventBindingContextProcessor(eventContext, eventQueueContextProcessor, eventExchangeContextProcessor);
    }

    @Bean("listenerContextProcessor")
    ListenerInvokeHolder<ListenerContext> listenerContextProcessor(EventContext eventContext,
                                                                   @Qualifier("eventRabbitAdmin") RabbitAdmin rabbitAdmin){
        return new ListenerContextProcessor(eventContext, rabbitAdmin);
    }

    @Bean("eventContextProcessor")
    EventContextProcessor eventContextProcessor(EventContext eventContext,
                                                EventQueueContextProcessor eventQueueContextProcessor,
                                                EventExchangeContextProcessor eventExchangeContextProcessor,
                                                EventBindingContextProcessor eventBindingContextProcessor,
                                                @Qualifier("listenerContextProcessor") ListenerInvokeHolder<ListenerContext> listenerContextProcessor) {
        List<EventContextHolder> eventContextHolderList = new ArrayList<EventContextHolder>(){{
            add(eventQueueContextProcessor);
            add(eventExchangeContextProcessor);
            add(eventBindingContextProcessor);
        }};
        return new EventContextProcessor(eventContext, eventQueueContextProcessor, eventBindingContextProcessor, eventContextHolderList, listenerContextProcessor);
    }

    @Bean
    EventContextEndpoint eventContextEndpoint(EventContext eventContext) {
        return new EventContextEndpoint(eventContext);
    }

    @ConditionalOnProperty(
            prefix = "event",
            name = "remoteType",
            havingValue = "rabbitmq")
    static class RabbitMQConfig {

        @Value("${event.rabbitmq.username}")
        String userName;

        @Value("${event.rabbitmq.password}")
        String password;

        @Value("${event.rabbitmq.host}")
        String host;

        @Value("${event.rabbitmq.port}")
        int port;

        @Value("${event.rabbitmq.virtual-host}")
        String virtualHost;

        @Value("${event.rabbitmq.prefetch-count:50}")
        Integer prefetchCount;

        @Bean(name = "eventConnectionFactory")
        CachingConnectionFactory cachingConnectionFactory() {
            CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
            cachingConnectionFactory.setUsername(userName);
            cachingConnectionFactory.setPassword(password);
            cachingConnectionFactory.setHost(host);
            cachingConnectionFactory.setPort(port);
            cachingConnectionFactory.setVirtualHost(virtualHost);
            cachingConnectionFactory.setPublisherConfirms(true); //TODO
            //cachingConnectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
            return cachingConnectionFactory;
        }

        @Bean(name = "eventSimpleRabbitListenerContainerFactory")
        @ConditionalOnMissingBean(name = "eventSimpleRabbitListenerContainerFactory")
        SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer) {
            SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
            factory.setMessageConverter(new Jackson2JsonMessageConverter());
            configurer.configure(factory, cachingConnectionFactory());
            return factory;
        }

        @Bean(name = "eventDirectRabbitListenerContainerFactory")
        @ConditionalOnMissingBean(name = "eventDirectRabbitListenerContainerFactory")
        DirectRabbitListenerContainerFactory rabbitListenerContainerFactory(DirectRabbitListenerContainerFactoryConfigurer configurer) {
            DirectRabbitListenerContainerFactory factory = new DirectRabbitListenerContainerFactory();
            factory.setMessageConverter(new Jackson2JsonMessageConverter());
            configurer.configure(factory, cachingConnectionFactory());
            return factory;
        }

        /*@ConditionalOnProperty(prefix = "event.listener", name = "enabled", havingValue = "true")
        @Bean(name = "eventSimpleMessageListenerContainer")
        @ConditionalOnMissingBean(name = "eventSimpleMessageListenerContainer")
        SimpleMessageListenerContainer simpleMessageListenerContainer(SimpleRemoteEventMessageListener simpleRemoteEventMessageListener, SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory)
                throws IOException {

            simpleRabbitListenerContainerFactory.createListenerContainer();

            SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
            container.setConnectionFactory(cachingConnectionFactory());
            container.setPrefetchCount(prefetchCount);
            container.setMessageListener(simpleRemoteEventMessageListener);
            container.addQueueNames(simpleRemoteEventMessageListener.getListeningQueues());
            container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
            return container;
        }*/

        /*@Bean(name = "eventTopicExchange")
        TopicExchange topicExchange() {
            TopicExchange topicExchange = new TopicExchange(Constants.EVENT_TOPIC_EXCHANGE, true, false);
            return topicExchange;
        }

        @Bean(name = "eventHeadersExchange")
        HeadersExchange headersExchange() {
            HeadersExchange headersExchange = new HeadersExchange(Constants.EVENT_HEADER_EXCHANGE, true, false);
            return headersExchange;
        }

        @Bean(name = "deadExchange")
        DirectExchange deadExchange() {
            DirectExchange deadExchange = new DirectExchange(Constants.EVENT_DEAD_EXCHANGE, true, false);
            return deadExchange;
        }*/

        /*@ConditionalOnProperty(prefix = "event.listener", name = "enabled", havingValue = "true")
        @Bean(name = "eventDirectMessageListenerContainer")
        @ConditionalOnMissingBean(name = "eventDirectMessageListenerContainer")
        DirectMessageListenerContainer directMessageListenerContainer(SimpleRemoteEventMessageListener simpleRemoteEventMessageListener)
                throws IOException {

            DirectMessageListenerContainer container = new DirectMessageListenerContainer();
            container.setConnectionFactory(cachingConnectionFactory());
            container.setPrefetchCount(prefetchCount);
            container.setMessageListener(simpleRemoteEventMessageListener); //TODO directMessageListener
            container.addQueueNames(simpleRemoteEventMessageListener.getListeningQueues());
            container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
            return container;
        }*/

        @Bean(name = "eventRabbitTemplate")
        RabbitTemplate rabbitTemplate(@Qualifier("eventConnectionFactory") CachingConnectionFactory cachingConnectionFactory) {
            return new RabbitTemplate(cachingConnectionFactory);
        }

        @Bean(name = "eventRabbitAdmin")
        RabbitAdmin rabbitAdmin(@Qualifier("eventRabbitTemplate") RabbitTemplate rabbitTemplate) {
            return new RabbitAdmin(rabbitTemplate);
        }

        @DependsOn({"eventContextProcessor","eventRabbitAdmin"})
        @Bean("eventQueuesInitializer")
        EventQueuesInitializer eventQueuesInitializer(EventContext eventContext,
                                                      @Qualifier("eventRabbitAdmin") RabbitAdmin rabbitAdmin) {
            return new EventQueuesInitializer(eventContext, rabbitAdmin);
        }

        @DependsOn({"eventContextProcessor","eventRabbitAdmin"})
        @Bean("eventExchangeInitializer")
        EventExchangeInitializer eventExchangeInitializer(EventContext eventContext,
                                                      @Qualifier("eventRabbitAdmin") RabbitAdmin rabbitAdmin) {
            return new EventExchangeInitializer(eventContext, rabbitAdmin);
        }

        @DependsOn({"eventQueuesInitializer","eventExchangeInitializer"})
        @Bean
        EventBindingInitializer eventBindingInitializer(EventContext eventContext,
                                                      @Qualifier("eventRabbitAdmin") RabbitAdmin rabbitAdmin) {
            return new EventBindingInitializer(eventContext, rabbitAdmin);
        }

        @ConditionalOnProperty(
                prefix = "event.listener",
                name = "enabled",
                havingValue = "true")
        @Bean
        ListenerContainerInitializer eventListenerContainerInitializer(EventContext eventContext,
                                                                       SimpleRemoteEventMessageListener simpleRemoteEventMessageListener,
                                                                       SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory){

            return new ListenerContainerInitializer(eventContext, prefetchCount, simpleRemoteEventMessageListener, cachingConnectionFactory(), simpleRabbitListenerContainerFactory);
        }

    }

    @ConditionalOnProperty(
            prefix = "event.listener",
            name = "enabled")
    class EventListenerConfig {

        @ConditionalOnMissingBean
        @ConditionalOnProperty(
                prefix = "event",
                name = "remoteType",
                havingValue = "rabbitmq",
                matchIfMissing = true)
        @DependsOn({"eventContext"})
        @Bean
        SimpleRemoteEventMessageListener simpleRemoteEventMessageListener(EventContext eventContext) {
            return new SimpleRemoteEventMessageListener(eventContext);
        }
    }

    @ConditionalOnProperty(
            prefix = "event.publisher",
            name = "enabled")
    static class EventPublisherConfig {

        @ConditionalOnMissingBean
        @ConditionalOnProperty(
                prefix = "event",
                name = "remoteType",
                havingValue = "rabbitmq")
        @Bean("simplePublisherAspect")
        SimplePublisherAspect simplePublisherAspect() {
            return new SimplePublisherAspect();
        }
    }

    @ConditionalOnProperty(
            prefix = "event.multicast",
            name = "enabled")
    static class EventMulticastConfig {

        @ConditionalOnMissingBean
        @ConditionalOnProperty(
                prefix = "event",
                name = "remoteType",
                havingValue = "rabbitmq")
        @Bean("simpleMulticasterAspect")
        SimpleMulticasterAspect simpleMulticasterAspect() {
            return new SimpleMulticasterAspect();
        }
    }

}
